﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Linq;
using System.Text;
using Input;
using FarseerPhysics.Dynamics;
using MapLibrary.DataLoading;
using FarseerPhysics;
using FarseerPhysics.DebugViews;
using MapLibrary.Players;
using Animation;
using AI;
using MapLibrary.Utility;
using MapLibrary.MapLibrary;
using MapLibrary.Particles;
using MapLibrary.Events;
using MapLibrary.Sound;
using Microsoft.Xna.Framework.Audio;

/**
 * This class is responsible for the management of the game world.  
 * This includes Level Loading, AI, Sprite Management, Physics.  The
 * game will always have an instance of World, and this instance should
 * never be destroyed until the game exits.
 */
namespace MapLibrary
{
    public class GameWorld : KeyboardInputListener, MouseInputListener
    {
        /// <summary>
        /// Delegate for events that are triggered on level
        /// load.
        /// </summary>
        public delegate void LevelInitializationDelegate();

        /// <summary>
        /// This event gets triggered whenever a level is loaded.  Register
        /// delegates to this method for anything that needs to get triggered
        /// when a level loads.
        /// </summary>
        public event LevelInitializationDelegate OnLevelLoad;

        
        /// <summary>
        /// RNG for the game
        /// </summary>
        public static Random Random = new Random();

        /// <summary>
        /// Set to true if you want Farseer physics debug output.
        /// </summary>
        public static bool DebugViewOn = false;

        public static Vector2 Gravity = new Vector2(0.0f, 9.82f);

        /**
         * The mGraphicsDevice is the drawing device for the game.  All game objects will
         * get drawn through this graphics device.
         */
        private GraphicsDevice mGraphicsDevice;

        /**
         * This is the content manager.  It is used to load levels, maps, sprites, etc.
         */
        private ContentManager mContentManager;

        /**
         * This sprite batch is the parent sprite batch where all game sprites and textures
         * will be drawn.
         */
        private SpriteBatch mSpriteBatch;

        /**
         * The viewport for the world.  All level objects will be drawn through
         * this viewport.  The viewports position will be updated by the WASD keys.
         */
        private WorldViewport mViewport;

        /**
         * This is the level that the world currently has loaded.
         */
        private Level mCurrentLevel;

        /**
         * This is the overarching level editor.  There is a constant reference to this
         * in the game world.  The level editor will get activated upon pressing the "z"
         * key by the game world by mEditor.Edit(Level).
         */
        private LevelEditor mEditor;

        /// <summary>
        /// The debug view for the Farseer physics engine.
        /// </summary>
        private DebugViewXNA dView;

        /// <summary>
        /// The physics World.
        /// </summary>
        private World physicsWorld;

        private ProjectileManager projectileManager;
        /// <summary>
        /// Persistence of player data accross level instances.
        /// </summary>
        private PlayerData playerData;

        private EventManager eventManager;

        /// <summary>
        /// AI system 
        /// </summary>
        private AISystem AI;

        /// <summary>
        /// Particle System
        /// </summary>
        private ParticleManager particleManager;

        /// <summary>
        /// Sound Manager
        /// </summary>
        private SoundManager soundManager;

        /**
         * Constructs a new World object on the specified device and content manager.  All
         * game world content will be loaded on the specified content manager, and all game
         * world graphics objects will be drawn on the specified graphics device.
         */
        public GameWorld(GraphicsDevice device, ContentManager manager)
        {
            mGraphicsDevice = device;
            mContentManager = manager;

            mSpriteBatch = new SpriteBatch(mGraphicsDevice);
            mViewport = new WorldViewport(device);

            mEditor = new LevelEditor(mGraphicsDevice, mViewport, manager);

            physicsWorld = new World(Gravity);
            Settings.ContinuousPhysics = true;

            dView = new DebugViewXNA(physicsWorld);
            playerData = new PlayerData();
            AI = new AISystem();

            projectileManager = new ProjectileManager(this);
            eventManager = new EventManager(this);
            
            soundManager = SoundManager.GetSoundManager();
            soundManager.Initialize(@"Content/sound/retrosound.xgs", @"Content/sound/Wave_Bank.xwb", @"Content/sound/Sound_Bank.xsb");
        }

        public ContentManager ContentManager
        {
            get
            {
                return mContentManager;
            }
        }

        public ProjectileManager ProjectileManager
        {
            get
            {
                return projectileManager;
            }
        }

        public WorldViewport Viewport
        {
            get { return mViewport; }
        }

        public PlayerData PlayerData
        {
            get { return playerData; }
        }

        /// <summary>
        /// The World where all Body objects will interact.
        /// </summary>
        public World PhysicsWorld
        {
            get { return physicsWorld; }
        }

        /// <summary>
        /// The level that is loaded currently.
        /// </summary>
        public Level CurrentLevel
        {
            get { return mCurrentLevel; }
        }

        public ParticleManager ParticleManager
        {
            get { return particleManager; }
        }

        /**
         * Draws all game world objects on the graphics device.  These objects are draw
         * through the game worlds specific sprite batch.  Note that if the level editor
         * is enabled, the level editor elements will be drawn as well.
         */
        public void Draw()
        {
            mSpriteBatch.Begin();

            if (!mEditor.Enabled)
                mCurrentLevel.Draw(mSpriteBatch, mViewport);
            else
                mEditor.Draw(mSpriteBatch);

            //draw debug information
            if (DebugViewOn)
            {
                //create projection matrix
                Matrix pers = Matrix.CreateOrthographicOffCenter(0f, ConvertUnits.ToSimUnits(800),
                                                             ConvertUnits.ToSimUnits(600), 0f, 0f,
                                                             1f);
                Matrix view = Matrix.CreateTranslation(
                    ConvertUnits.ToSimUnits(-mViewport.X),
                    ConvertUnits.ToSimUnits(-mViewport.Y),
                    0.0f
                    );

                dView.RenderDebugData(ref pers, ref view);
            }

            eventManager.Draw(mSpriteBatch);

            mSpriteBatch.End();
        }

        /**
         * Prepares the World for use in the game.
         */
        public void LoadWorld()
        {
            mEditor.LoadEditor(mContentManager);
            particleManager = new ParticleManager(this);
            //temporary - only loads a specific level
            LevelData temp = mContentManager.Load<LevelData>(@"testing");
            mCurrentLevel = BuildLevel(temp);
            LoadPlayer();
            //Notify listeners that the level has been loaded.
            if (OnLevelLoad != null)
                OnLevelLoad();
            dView.LoadContent(mSpriteBatch.GraphicsDevice, mContentManager);

            SpriteType bulletType = mContentManager.Load<SpriteType>(@"./Animation/SpriteTypes/Bullet");
            projectileManager.Load(mContentManager);

            //handle all debugger flags
            dView.AppendFlags(DebugViewFlags.AABB);
            dView.AppendFlags(DebugViewFlags.ContactPoints);
            dView.AppendFlags(DebugViewFlags.PolygonPoints);
            dView.AppendFlags(DebugViewFlags.DebugPanel);
            dView.AppendFlags(DebugViewFlags.CenterOfMass);
            dView.AppendFlags(DebugViewFlags.ContactNormals);
            dView.AppendFlags(DebugViewFlags.Controllers);
            dView.AppendFlags(DebugViewFlags.Shape);
            dView.AppendFlags(DebugViewFlags.Pair);
        }

        public void UpdateMouseInput(GameInput Input)
        {
            if (mCurrentLevel != null)
            {
                if (mCurrentLevel.Player != null)
                    mCurrentLevel.Player.UpdateMouseInput(Input);
            }

            projectileManager.UpdateMousePosition(Input.GetMousePosition());
        }

        public void Update(GameTime time)
        {
            if (GameStateManager.Instance.IsCurrentState(GameStateManager.GameState.InGame))
            {
                if (!mEditor.Enabled)
                {
                    eventManager.Update();
                    AI.update(mCurrentLevel, physicsWorld);
                    mCurrentLevel.UpdateAnimations();
                    mViewport.scrollScreen();

                    physicsWorld.Step(Math.Min((float)time.ElapsedGameTime.TotalMilliseconds * 0.001f, (1f / 30f)));

                    mCurrentLevel.PostUpdate(physicsWorld);
                }
            }
        }


        /// <summary>
        /// Building a level from data comes in 2 stages
        /// 
        /// 1) Init.  This stage loads the map and other non-collision
        /// related data.
        /// 
        /// 2) Registration.  This stage sets up all of the collision
        /// data in the World.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private Level BuildLevel(LevelData data)
        {
            Level build = new Level(this);
            build.Init(data, mContentManager);
            build.Register(data, this, mContentManager);

            return build;
        }

        /// <summary>
        /// Call this after loading the level.  It will create the Player
        /// with the appropriate data set from playerData.
        /// </summary>
        private void LoadPlayer()
        {
            Sprite s = SpriteFactory.CreatePlayer(this, playerData, new Vector2(100,100));
            //mCurrentLevel.Sprites.Add(s);
            mCurrentLevel.RegisterPlayer((Player)s);
            mViewport.setPlayer((Player)s);
        }

        /**
         * Modify the world through the keyboard input.  Right now this mostly involves
         * moving the world's viewport and enabling editing.
         */
        public void UpdateKeyboardInput(GameInput Input)
        {
            if (mCurrentLevel != null)
            {
                if (mCurrentLevel.Player != null)
                    mCurrentLevel.Player.UpdateKeyboardInput(Input);
            }

            if (Input.IsKeyDown(Keys.NumPad4))
                mViewport.MoveLeft();

            if (Input.IsKeyDown(Keys.NumPad8))
                mViewport.MoveUp();

            if (Input.IsKeyDown(Keys.NumPad6))
                mViewport.MoveRight();

            if (Input.IsKeyDown(Keys.NumPad5))
                mViewport.MoveDown();

            if (Input.IsKeyPressed(Keys.Z))
            {
                if (mEditor.Enabled)
                    mEditor.Disable();
                else
                    mEditor.LoadEditing(mCurrentLevel, mContentManager);
            }
        }

        /**
         * Also registers the editor as a mouse listener.
         */
        public void OnMouseRegister(GameInput Input)
        {
            Input.AddMouseListener(mEditor);
        }

        /**
         * Also registers the editor as a keyboard listener.
         */
        public void OnKeyboardRegister(GameInput Input)
        {
            Input.AddKeyboardListener(mEditor);
        }
    }
}
